/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License specifically applicable to
 * this software at http://developer.yahoo.com/yslow/license.txt, or the general
 * form at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is: YSlow
 *
 * The Initial Developer of the Original Code is Yahoo! Inc.
 *
 * Copyright (C) 2007, Yahoo! Inc. All Rights Reserved.
 *
 * Contributor(s):
 * ***** END LICENSE BLOCK ***** */

//Create the Utils namespace within the global YSLOW namespace.
if ( typeof YSLOW.Utils == "undefined") {
	YSLOW.Utils = {};
}

YSLOW.Utils.expiresMonth = { "Jan": "1",
							   "Feb": "2",
							   "Mar": "3",
							   "Apr": "4",
							   "May": "5",
							   "Jun": "6",
							   "Jul": "7",
							   "Aug": "8",
							   "Sep": "9",
							   "Oct": "10",
							   "Nov": "11",
							   "Dec": "12"
							};

// Make these names singular - they will be pluralized if necessary in the code.
YSLOW.Utils.prettyTypes = { "image": "Image",
							  "doc": "HTML/text",
							  "cssimage": "CSS Image",
							  "css": "Stylesheet File",
							  "js": "JavaScript File",
							  "flash": "Flash Object",
							  "iframe": "IFrame/Frame",
                              "xhr": "XMLHttpRequest"
							};


//YSlow Utils
//Give it an element and the property (width, background) and depending upon the browser, it wraps
//the elements. This uses YUI library.
YSLOW.Utils.getStyle = function(el, property) {
	var value = null;
	var dv = FirebugContext.yslowContext["document"].defaultView;

	if (el.style[property]) {
		value = el.style[property];
	}
	else if (el.currentStyle && el.currentStyle[property]) {
		value = el.currentStyle[property];
	}
	else if ( dv && dv.getComputedStyle ) {  // convert camelCase to hyphen-case
		var converted = '';
		for(var i = 0, len = property.length;i < len; ++i) {
			if (property.charAt(i) == property.charAt(i).toUpperCase()) {
				converted = converted + '-' + property.charAt(i).toLowerCase();
			} else {
				converted = converted + property.charAt(i);
			}
		}
		if (dv.getComputedStyle(el, '') && dv.getComputedStyle(el, '').getPropertyValue(converted)) {
			value = dv.getComputedStyle(el, '').getPropertyValue(converted);
		}
	}

	return value;
}





// Try to find the right syntax for creating an XML object.
YSLOW.Utils.getXmlObject = function() {
    var xmlObj = false;

	try {
		xmlObj = new XMLHttpRequest();
	}
	catch(e){
		var _msxml_progid = ['MSXML2.XMLHTTP.5.0', 'MSXML2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP'];
		for ( var i=0; i < _msxml_progid.length; ++i ) {
			try {
				xmlObj = new ActiveXObject(_msxml_progid[i]);
			}
			catch(e) {
			}
		}
	}
	finally {
		return xmlObj;
	}
}







////////////////////////////////////////////////////////////////////////////////
// UTILS
////////////////////////////////////////////////////////////////////////////////

var gDprintLevel = 0;

function setDprint(level) {
	gDprintLevel = level;
}


function dprint(level, msg) {
    if ( level > gDprintLevel )
        return;

    ddd(msg);
}


function eprint(msg) {
    ddd(msg);
}


function printObject(obj, sKey, bNoFunctions) {
    var msg = "";
    for ( var key in obj ) {
        if ( !sKey || (-1 != key.indexOf(sKey)) ) {
            if ( "function" == typeof(obj[key]) ) {
                if ( !bNoFunctions ) {
                    msg += key + " = [function]\n";
                }
            }
            else {
                msg += key + " = " + obj[key] + "\n";
            }
        }
        if ( msg.length > 400 ) {
            eprint(msg);
            msg = "";
        }
    }
    eprint(msg);
}



////////////////////////////////////////////////////////////////////////////////
// UTILS END
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// PROGRESS BAR
////////////////////////////////////////////////////////////////////////////////


YSLOW.Utils.createProgressBar = function() {
	var textStyle = "color: #000000; font-size: 10pt; font-family: Arial,Helvetica;";
	var progTextStyle = "margin-left: 10px; color: #000000; font-size: 8pt; font-family: Arial,Helvetica;";

	var outerbarStyle = "position:relative; height:14px; width: 200px; vertical-align: middle; background-color: #CCC; border: 1px solid #666666; margin-left: 10px;";
	var progbarStyle = "position:absolute; top:0px; left:0px; height: 14px; width: 200px; font-size: 0px; background-color: #FFFFFF;";

	var sBody = '' +
'<div class="about"><p style="margin: 0px 0px 2px 0px; ' + textStyle + '">\n' +
'Finding components in the page:<br>\n' +
'<div id="outerbar" style="' + outerbarStyle + '">\n' +
'  <div id="progbar" style="' + progbarStyle + '"></div>\n' +
'</div>\n' +
'<p style="margin: 10px 0px 2px 0px; ' + textStyle + '">\n' +
'Getting component information:<br>\n' +
'<div id="outerbar2" style="' + outerbarStyle + '">\n' +
'  <div id="progbar2" style="' + progbarStyle + '"></div>\n' +
'</div>\n' +
'<div id="progtext" style="' + progTextStyle + '">start...</div>' +
'</div>';

	FirebugContext.yslowContext.setButtonView('panel_about', sBody);
}


// percent should be an integer from 0 to 100 inclusive
YSLOW.Utils.setProgress = function(percent) {
    if ( 0 > percent ) {
	    percent = 0;
	}
	else if ( 100 < percent ) {
	    percent = 100;
	}

	var outerbar = FirebugContext.getPanel('yslow').document.getElementById(FirebugContext.yslowContext["outerbarName"]);
	var progbar = FirebugContext.getPanel('yslow').document.getElementById(FirebugContext.yslowContext["progbarName"]);
	if ( outerbar && progbar ) {
		var maxwidth = outerbar.clientWidth;
		percent = 100 - percent;
		var width = (maxwidth*percent)/100;
		if ( width > maxwidth ) {
		    width = maxwidth;
		}
		var left = maxwidth - parseInt(width);
		progbar.style["width"] = parseInt(width) + "px";
		progbar.style["left"] = parseInt(left) + "px";
	}
}


// Set the text below the progress bar.
YSLOW.Utils.setProgressText = function(sText) {
	var progtext = FirebugContext.getPanel('yslow').document.getElementById("progtext");
	if ( "string" == typeof(sText) && progtext ) {
		progtext.innerHTML = sText;
	}
}


// Set the progress bar back to zero.
YSLOW.Utils.resetBar = function() {
	var progbar = FirebugContext.getPanel('yslow').document.getElementById(FirebugContext.yslowContext["progbarName"]);
	if ( progbar ) {
		progbar.style.width = 0;
	}
}

// In case you do not like my div names.
YSLOW.Utils.setBarNames = function(outerbarname, progbarname) {
	FirebugContext.yslowContext["outerbarName"] = outerbarname;
	FirebugContext.yslowContext["progbarName"] = progbarname;
}

////////////////////////////////////////////////////////////////////////////////
// PROGRESS BAR END
////////////////////////////////////////////////////////////////////////////////




YSLOW.Utils.formatFloat = function(pFloat, precision, suffix) {
	if ( precision < 1 ) {
		precision = 1;
	}

	var denom = Math.pow(10, precision);
	pFloat = parseInt(pFloat * denom)/denom;

	return pFloat + ( suffix ? suffix : "" );
}






// Return true if we find the URL's filename in the HTML.
YSLOW.Utils.parseFindUrlFilename = function(sUrl, sHtml) {
	var iSlash = sUrl.lastIndexOf("/");
	if ( -1 != iSlash ) {
		var sFilename = sUrl.substring(iSlash+1);
		if ( -1 != sHtml.indexOf(sFilename) ) {
			return true;
		}
	}

	return false;
}




// Make a pretty string out of an Expires date (eg, "Fri, 16 Oct 2015 20:08:00 GMT").
YSLOW.Utils.prettyExpiresDate = function(sExpires) {
    //dprint(9, "YSLOW.Utils.prettyExpiresDate: enter function");
	var aDate = sExpires.split(" ");

	if ( aDate.length < 4 ) {
		return "";
	}

	var sDay = aDate[1];
	if ( "0" == sDay.substring(0, 1) ) {
		sDay = sDay.substring(1); // strip a leading zero
	}
	var sMonth = aDate[2];
	var sYear = aDate[3];
	return YSLOW.Utils.expiresMonth[sMonth] + "/" + sDay + "/" + sYear;
}




// Return the number of epoch MILLIseconds for a date in the Expires format (eg, "Fri, 16 Oct 2015 20:08:00 GMT").
YSLOW.Utils.epochMS = function(sExpires) {
    //dprint(9, "YSLOW.Utils.epochMS: enter function");

	// I tested both FF and IE and they accept a string like "Fri, 16 Oct 2015 20:08:00 GMT".
	// This number of milliseconds is in the local timezone, so it'll work correctly when compared to "new Date()".
	return Number ( new Date(sExpires) );
}


// Returns:
//   0 - it IS a CDN
//   1 - it is NOT a CDN
//  -1 - it is NOT a CDN, but it's an exception
YSLOW.Utils.isNotCDN = function(url) {
    //dprint(9, "YSLOW.Utils.isNotCDN: enter function");
	// All of the URLs are supposed to be full URLs.
	var iSlashSlash = url.indexOf("//");
	var iPath = url.indexOf("/", iSlashSlash+2);
	var hostname = url.substring(iSlashSlash+2, iPath);

	// music.yahoo.com (launch)
	var regexLaunch = new RegExp("^http://[^\.]*\\.g\\.akamai\\.net");
    if ( url.match(regexLaunch) ) {
		// It IS a CDN.
		return 0;
	}

	// Typical akamai format
    var regex1 = new RegExp("^http://([^\.]*)\\.([^\.]*)\\.yimg\\.com/[^/]*\\.yimg\\.com/.*$");
    var regex2 = new RegExp("^http://([^\.]*)\\.([^\.]*)\\.yimg\\.com/[^/]*\\.yahoo\\.com/.*$");
    if ( url.match(regex1) || url.match(regex2) ) {
		// It IS a CDN.
		return 0;
	}

	// https
	if ( "https:" == url.substring(0, 6) ) {
		if ( 0 == url.indexOf("https://sec.yimg.com/") || 0 == url.indexOf("https://a248.e.akamai.net/") ) {
			// It IS a CDN.
			return 0;
		}
	}

	// Domains that ARE CDNs
	var aOtherCDNs = [
					   "http://images.autotrader.com",  // Vy Phan asked to have this added
                       "http://adz.kr.yahoo.com",  // KR (Jaguar) CDN
                       "http://img.news.yahoo.co.kr",  // KR (Jaguar) CDN
                       "http://img.srch.yahoo.co.kr",  // KR (Jaguar) CDN
                       "pimg.kr.yahoo.com",  // KR (Jaguar) CDN
                       "http://kr.img.n2o.yahoo.com",  // KR (Jaguar) CDN
                       "http://img.shopping.yahoo.co.kr ",  // KR (Jaguar) CDN
					   "cn.yimg.com",                       // China CDN (requested by Yongming Zhao)
					   "s3.amazonaws.com",          // S3 - colos in CA and VA
					   "akamai.com",
                       "http://img.yahoo.co.kr"		// KR (Jaguar) CDN
                      ];

	// Add CDNs specified by the user's preferences.
	var CDNs = YSLOW.getPref("yslow.cdnHostnames", "");
	if ( CDNs ) {
		// Need to do this because if CDNs is "" it'll add an empty string to aOtherCDNs, which matches everything!
		aOtherCDNs = aOtherCDNs.concat(CDNs.split(","));
	}

	for ( var i = 0; i < aOtherCDNs.length; i++ ) {
		if ( -1 != url.indexOf(aOtherCDNs[i]) ) {
			// It IS a CDN.
	        return 0;
        }
	}

	// Exceptions that CAN'T be moved to a CDN.
	var aExceptions = ["http://chart.yahoo.com",	// This isn't Akamaized, but it really can't be (dynamic financial charts).
                       "http://f3.yahoofs.com",         // Images for 360.
                       "http://f5.yahoofs.com",         // Images from YMDB.
                       "http://f3c.yahoofs.com",        // Images for Shopping from YMDB.
                       "http://f5c.yahoofs.com",        // Images for Shopping from YMDB.
                       "http://us.a1c.yahoofs.com",     // Personals photos
                       "http://a1.yahoofs.com",         // Images from YMDB
                       "http://us.f3.yahoofs.com"       // YMDB, personal photos that can't be akamaized
                      ];
	for ( var i = 0; i < aExceptions.length; i++ ) {
		if ( aExceptions[i] == url.substring(0, aExceptions[i].length) ) {
			// It's an exception.
	        return -1;
        }
	}

	if ( ! YSLOW.Utils.isNotYCS(url) ) {
		// It IS a CDN.
		return 0;
	}

	// It is NOT a CDN.
	return 1;
}


YSLOW.Utils.isNotYCS = function(url) {
    //dprint(9, "YSLOW.Utils.isNotYCS: enter function");
    // All of the URLs are supposed to be full URLs.

	var aPrefixes = ["http://d.yimg.com/",
		             "http://e.yimg.com/",
		             "http://h.yimg.com/",
		             "http://l.yimg.com/",
		             "http://p.yimg.com/",
		             "http://ads.yimg.com/",
		             "http://mail.yimg.com/",
		             "http://maps.yimg.com/",
		             "http://us.js2.yimg.com/",  // will be moved
		             "http://us.a2.yimg.com/",   // will be moved
		             "http://s1.yimg.com/",      // not yet, but eventually
		             "http://yui.yahooapis.com/"];  // not really YCS, but will put it here

    for ( var i = 0; i < aPrefixes.length; i++ ) {
        if ( 0 == url.indexOf(aPrefixes[i]) ) {
            return 0; // YCS'd
		}
    }

    return 1; // NOT YCS'd
}


// Return a letter grade for a score.
YSLOW.Utils.prettyScore = function(iScore, bDark, bText) {
    //dprint(9, "YSLOW.Utils.prettyScore: enter function");

	if ( ! parseInt(iScore) && 0 != iScore ) {
		return iScore;
	}

	var sLetter = "F";

	if ( 90 <= iScore )
		sLetter = "A";
	else if ( 80 <= iScore )
		sLetter = "B";
	else if ( 70 <= iScore )
		sLetter = "C";
	else if ( 60 <= iScore )
		sLetter = "D";

	if ( bText ) {
		return sLetter;
	}

	return sLetter;
}


// Return a color for a score.
YSLOW.Utils.colorScore = function(iScore, bDark) {
    //dprint(9, "YSLOW.Utils.colorScore: enter function");
	var sResult = ( bDark ? "#FFFF33" : "#FFFF8C" );

	if ( 90 <= iScore )
		sResult = ( bDark ? "#33FF33" : "#8CFF8C" );
	else if ( 60 > iScore )
		sResult = ( bDark ? "#FF3333" : "#FF8C8C" );

	return sResult;
}



// Find the HTML doc and return its (estimated?) size.
YSLOW.Utils.getHtmlDocSize = function() {
	var parentDoc = FirebugContext.yslowContext.getParentDocComponent();

	var results = {};
	if ( YSLOW.Component.getCompVal(parentDoc, "content-length") ) {
		results["size"] = YSLOW.Component.getCompVal(parentDoc, "content-length");
	}
	else if ( YSLOW.Component.getCompVal(parentDoc, "ungzipped_size") ) { // it only has an ungzipped_size if it was gzipped
		var ungzipped_size = YSLOW.Component.getCompVal(parentDoc, "ungzipped_size");
		var gzip_estimate = parseInt(ungzipped_size/3);
		results["size"] = gzip_estimate;
		results["estimate"] = true;
		results["footnote"] = ' (<a class="rule" style="text-decoration: underline;" href="javascript:;" onclick="javascript:alert(\'This HTML document was gzipped, but the gzipped size was not specified in the HTTP response headers. The estimated gzip size (' + YSLOW.Utils.kbSize(gzip_estimate) + ') is calculated as one third of the ungzipped size (' + YSLOW.Utils.kbSize(ungzipped_size) + ').\');">est</a>)';
	}

	return results;
}


// Convert a number of bytes into a readable KB size string.
YSLOW.Utils.kbSize = function(size) {
    //dprint(9, "YSLOW.Utils.kbSize: enter function");
	var remainder = size % (size > 100 ? 100 : 10);
	size -= remainder;
	return parseFloat(size/1000) + ( 0 == (size % 1000) ? ".0" : "" ) + "K";
}



// Return a string with an anchor around a long piece of text.
// (It's confusing, but often the "long piece of text" is the URL itself.)
// Snip the long text if necessary.
// Optionally, break the long text across multiple lines.
YSLOW.Utils.prettyAnchor = function(text, url, sClass, bBriefUrl, maxChars, numLines, rel) {
    //dprint(9, "YSLOW.Utils.prettyAnchor: enter function");

	if ( "undefined" == typeof(url) ) {
		url = text;
	}
	if ( "undefined" == typeof(sClass) ) {
		sClass = "";
	}
	else {
		sClass = " class=\"" + sClass + "\"";
	}
	if ( "undefined" == typeof(maxChars) ) {
		maxChars = 100;
	}
	if ( "undefined" == typeof(numLines) ) {
		numLines = 1;
	}
    rel = (rel) ? ' rel="'+ rel +'"' : '';

	var sTitle = "";
	if ( bBriefUrl ) {
		text = YSLOW.Utils.briefUrl(text, maxChars);
		sTitle = " title='" + url + "'";
	}

	var sResults = "";
	var iLines = 0;
	while ( 0 < text.length ) {
		sResults += "<a" + rel + sClass + sTitle + " href='" + url + "' onclick='document.yslowPanel.openLink(\"" + url + "\"); return false;'>" + text.substring(0, maxChars);
		text = text.substring(maxChars);
		iLines++;
		if ( iLines >= numLines ) {
			// We've reached the maximum number of lines.
			if ( 0 < text.length ) {
				// If there's still text leftover, snip it.
				sResults += "[snip]";
			}
			sResults += "</a>";
			break;
		}
		else {
			// My (weak) attempt to break long URLs.
			sResults += "</a><font style='font-size: 0px;'> </font>";
		}
	}
	return sResults;
}

//This function will generate the code for embedding the magnifying glass image with all the required code.
YSLOW.Utils.getHeaderImageEmbedCode = function(headersDivId) {
	if ( FirebugContext.yslowContext.bPrintable ) {
		return "";
	}
	var sHeaderText = '<a href="javascript:document.yslowContext.viewHeaders(\'' + headersDivId + '\')"' +    // use onclick so we get the event, but put this in the href for users to see
					  ' onclick="document.yslowContext.viewHeaders(\'' + headersDivId + '\', event); return false;">' +
					  '<img border=0 src="http://us.i1.yimg.com/us.yimg.com/i/rt/yslow/magnify.gif" title="HTTP headers" width=11 height=10></a>&nbsp;' ;

	return sHeaderText;
}

// Just do the hostname and first few chars after the last slash.
YSLOW.Utils.briefUrl = function(url, maxchars) {

    maxchars = maxchars || 100; // default 100 characters

    // We assume it's a full URL.
	var iDoubleSlash = url.indexOf("//");
	if ( -1 != iDoubleSlash ) {

        // remove query string
        var iQMark = url.indexOf("?");
        if ( -1 != iQMark ) {
            url = url.substring(0, iQMark) + "?...";
        }

        if (url.length > maxchars) {
            var iFirstSlash = url.indexOf("/", iDoubleSlash+2);
            var iLastSlash = url.lastIndexOf("/");
            if ( -1 != iFirstSlash && -1 != iLastSlash && iFirstSlash != iLastSlash ) {
                url = url.substring(0, iFirstSlash+1) + "..." + url.substring(iLastSlash);
            }
        }
	}

	return url;
}

// Convert a type (eg, "cssimage") to a prettier name (eg, "CSS Images").
YSLOW.Utils.prettyType = function(sType) {
    //dprint(9, "YSLOW.Utils.prettyType: enter function");
	return YSLOW.Utils.prettyTypes[sType];
}




////////////////////////////////////////////////////////////////////////////////
// UTILS
////////////////////////////////////////////////////////////////////////////////

// Print the full path from window to the specified object.
YSLOW.Utils.printPath = function(obj, iGen) {
	if ( "undefined" == typeof(iGen) ) {
		iGen = 1;
	}

	if ( "undefined" == typeof(obj.parentNode) || !obj.parentNode || obj == obj.parentNode ) {
		return "document";
	}
	else if ( iGen > 20 ) {
		alert("20 Generations: ");
		return "20 Generations";
	}
	else {
		var sObj = "";
		for ( var i = 0; i < obj.parentNode.childNodes.length; i++ ) {
			if ( obj == obj.parentNode.childNodes[i] ) {
				sObj = "childNodes[" + i + "]";
			}
		}
		var sPath = YSLOW.Utils.printPath(obj.parentNode, iGen++) + "." + sObj;
		return sPath;
	}

	return "";
}


YSLOW.Utils.escapeHtml = function(html) {
	var result = html;
	result = result.replace(new RegExp("<","g"), "&lt;");
	result = result.replace(new RegExp(">","g"), "&gt;");
	return result;
}


// Dynamically add a stylesheet to the document.
YSLOW.Utils.loadCss = function(url, doc) {
    //dprint(9, "YSLOW.Utils.loadCss: enter function");

	if ( ! doc ) {
		doc = FirebugContext.yslowContext["document"];
	}

	var newCss = doc.createElement("link");
	newCss.rel = "stylesheet";
	newCss.type = "text\/css";
	newCss.href = url;
	doc.body.appendChild(newCss);
	dprint(5, "YSLOW.Utils.loadCss: loaded stylesheet '" + url + "'.");

	return newCss;
}


// Dynamically add a script to the document.
YSLOW.Utils.loadJs = function(url, doc) {
    //dprint(9, "loadJs: enter function");

	if ( ! doc ) {
		doc = FirebugContext.yslowContext["document"];
	}

	var element = doc.createElement("script");
	element.src = url;
	doc.body.appendChild(element);

	return element;
}

YSLOW.Utils.mod = function(divisee, base) {
	return Math.round(divisee - (Math.floor(divisee/base)*base));
}


// For sorting arrays numerically.
YSLOW.Utils.compareNumbers = function(a, b) {
	return a - b;
}


////////////////////////////////////////////////////////////////////////////////
// UTILS END
////////////////////////////////////////////////////////////////////////////////


// Return an array of elements that match the tagname AND the classname.
// "classname" can be a comma-delimited list of classnames.
YSLOW.Utils.getElementsByTagNameAndClass = function(tagname, classname, parent) {
    if ( "undefined" == typeof(parent) ) {
	    parent = FirebugContext.yslowContext["document"];
	}

	var aClassnames = classname.split(",");
	var hClassnames = new Array();
	for( var i = 0; i < aClassnames.length; i++ ) {
	    hClassnames[aClassnames[i]] = 1;
	}

    var aElements = parent.getElementsByTagName(tagname);
	var aResults = new Array();
	for (var i = 0; i < aElements.length; i++) {
	    var element = aElements[i];
		if ( hClassnames[element.className] ) {
		    aResults[aResults.length] = element;
		}
	}

	return aResults;
}


YSLOW.Utils.setStatusBar = function(text, sId) {
	sId = sId || 'yslowStatusGrade';
	var sbelem = FirebugChrome.window.document.getElementById(sId);
	sbelem.value = text;
}


YSLOW.Utils.clearStatusBar = function() {
	YSLOW.Utils.setStatusBar("YSlow", "yslowStatusGrade");
	YSLOW.Utils.setStatusBar("", "yslowStatusSize");
	YSLOW.Utils.setStatusBar("", "yslowStatusTime");
}


YSLOW.Utils.toggleStatusBar = function(bHide) {
	FirebugChrome.window.document.getElementById('yslowStatusBar').hidden = bHide;
}



//	Detect the users browser setting and accordingly open links in new window or new tab in the same window.
YSLOW.Utils.openLink = function(url) {
	if ( YSLOW.getPref("browser.link.open_external") == 3) {
		gBrowser.selectedTab = gBrowser.addTab(url);
	}
	else {
		window.open(url, "_blank")
	}
}


YSLOW.Utils.getFileHeader = function (file, headerName) {
	headerName = headerName.toLowerCase();

	if ( file.responseHeaders ) {
		for ( var i = 0; i < file.responseHeaders.length; i++ ) {
			var hHeader = file.responseHeaders[i];
			if ( headerName == hHeader['name'].toLowerCase() ) {
				return hHeader['value'];
			}
		}
	}

	return "";
}


YSLOW.Utils.DateHeaderToEpoch = function (sDate) {
	// Assume the Date HTTP header is properly formatted.
	return parseInt((new Date(sDate)).getTime() / 1000);
}


// Use some heuristics for determining if the file actually generated any HTTP traffic.
YSLOW.Utils.validHttpRequest = function(file) {
	//dprint(9, "validHttpRequest(" + file.href + "): enter\n");

	// If there are no HTTP headers...
	if ( !file.responseHeaders ) {
		dprint(8, "YSLOW.Utils.validHttpRequest(" + file.href + "): not valid: no response headers\n");
		return false;
	}

	// If there is not start time for loading...
    if ( !file.startTime ) {
		dprint(8, "YSLOW.Utils.validHttpRequest(" + file.href + "): not valid: no start time\n");
		return false;
	}

	// If the load time is 0...
    if ( file.endTime && 0 == (file.endTime - file.startTime) ) {
		dprint(8, "YSLOW.Utils.validHttpRequest(" + file.href + "): not valid: (near) zero load time\n");
		return false;
	}

	// If there's no file status, it's probably not an HTTP request.
	if ( "undefined" == typeof(file.status) ) {
		dprint(8, "YSLOW.Utils.validHttpRequest(" + file.href + "): not valid: no file status\n");
		return false;
	}

	// Only worry about 200 responses read from cache.
	if ( 200 == file.status ) {
		var contentType = YSLOW.Utils.getFileHeader(file, 'Content-Type');

		// If there's no content-type header, it's not a valid 200 response
		if ( !contentType ) {
			dprint(8, "YSLOW.Utils.validHttpRequest(" + file.href + "): not valid: no content-type\n");
			return false;
		}

		var sDateHeader = YSLOW.Utils.getFileHeader(file, 'Date');
		if ( sDateHeader ) {
			var dateEpoch = YSLOW.Utils.DateHeaderToEpoch(sDateHeader);
			var t_start = parseInt(file.startTime/1000);
			var bDateIsValid = (dateEpoch+1) >= t_start; // add 1 to be conservative in ties

			if ( -1 != contentType.toLowerCase().indexOf("image") ) {
				var elapsedTime = file.endTime - file.startTime;
				dprint(8, "validHttpRequest(" + file.href + "): " + ( bDateIsValid || elapsedTime > 20 ? "valid" : "not valid: date not valid and elapsedTime <= 10" ) + "\n");
				return ( bDateIsValid || elapsedTime > 10 );
			}
			else if ( -1 != contentType.toLowerCase().indexOf("html") ) {
				var elapsedTime = file.endTime - file.startTime;
				dprint(8, "validHttpRequest(" + file.href + "): " + ( bDateIsValid || elapsedTime > 20 ? "valid" : "not valid: date not valid and elapsedTime <= 20" ) + "\n");
				return ( bDateIsValid || elapsedTime > 20 );
			}
			else {
				dprint(8, "validHttpRequest(" + file.href + "): " + ( bDateIsValid ? "valid" : "not valid: date not valid" ) + "\n");
			    return 	bDateIsValid;
            }
		}

	}

    // Assume it IS a real HTTP request.
	dprint(8, "validHttpRequest(" + file.href + "): valid\n");
	return true;
}


// This is a copy of the updateRowData from Firebug's net.js file,
// with a few lines added to fix some bugs in Net Panel.
//
// The desired behavior in Net Panel is that components that are read from
// the browser's disk cache are shown with a lighter gray download bar (using
// the "fromCache" class). There were bugs with this behavior:
//   1. Scripts (.js) and stylesheets (.css) that are read from disk cache
//      were shown with a dark bar, making people think there was an HTTP request.
//   2. Images that are read from disk cache WITHOUT A 304 CONDITIONAL GET REQUEST
//      were also shown with a dark bar.
//
// I've marked my fixes clearly below.
//
YSLOW.Utils.updateRowData = function (rightNow) {
	var queue = this.queue;
	this.queue = [];

	var phase;
	var newFileData = [];
	var bPatchNetPanel = YSLOW.getPref("yslow.patchnetpanel");

	for (var i = 0; i < queue.length; ++i)
	{
		var file = queue[i];
		phase = this.calculateFileTimes(file, phase, rightNow);
		var nPrevNewFiles = newFileData.length;
		this.updateFileRow(file, newFileData);

		// STEVE'S FIX: If the file is (incorrectly) flagged as not "fromCache" but it
		// did not actually generate any HTTP traffic, don't show it in Net Panel.
		if ( bPatchNetPanel && !file.fromCache && newFileData.length > nPrevNewFiles && !YSLOW.Utils.validHttpRequest(file) ) {
			newFileData.pop();
		}

		file.invalid = false;
		this.invalidatePhase(file.phase);
	}

	if (newFileData.length)
	{
		var tbody = this.table.firstChild;
		var lastRow = tbody.lastChild.previousSibling;
		var row = this.fileTag.insertRows({files: newFileData}, lastRow)[0];

		for (var i = 0; i < newFileData.length; ++i)
		{
			var file = newFileData[i].file;
			row.repObject = file;
			file.row = row;
			row = row.nextSibling;
		}
	}
}


// Return the prototype for a panel - initially used to patch Net Panel
YSLOW.Utils.getPanelPrototype = function (sName) {
	for ( var i = 0; i < Firebug.panelTypes.length; i++ ) {
		if ( sName == Firebug.panelTypes[i].prototype.name ) {
			return Firebug.panelTypes[i].prototype;
		}
	}

	return null;
}


YSLOW.Utils.getOptionsMenuItemsNet = function() {
	return [
   	        FBL.optionMenu("DisableNetMonitor", "disableNetMonitor"),
			YSLOW.Utils.getPanelPrototype('yslow').optionMenuYSlow("Patch Net Panel from YSlow", "yslow.patchnetpanel")
        ];
}


// Fix a bug in Firebug's Net Panel.
YSLOW.Utils.patchNetPanel = function () {
	var netPanelPrototype = YSLOW.Utils.getPanelPrototype('net');

	if ( netPanelPrototype ) {
		// Override the prior definition of this function.
		netPanelPrototype.updateRowData = YSLOW.Utils.updateRowData;
		netPanelPrototype.getOptionsMenuItems = YSLOW.Utils.getOptionsMenuItemsNet;
	}
}

